// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 页面堆。
// 
// 见malloc。浏览概览。

package runtime

import (
	"internal/cpu"
	"internal/goarch"
	"runtime/internal/atomic"
	"unsafe"
)

const (
	// minPhysPageSize是物理页面大小的下限。
	// 实际页面大小可能大于此值。相比之下，
	// sys。PhysPageSize是物理页面大小的上限。
	minPhysPageSize = 4096

	// maxPhysPageSize是运行时支持的最大页面大小。
	maxPhysPageSize = 512 << 10

	// maxPhysHugePageSize设置运行时支持的最大巨大页面大小的上限
	// 。
	maxPhysHugePageSize = pallocChunkBytes

	// pagesperclaimerchunk表示一次从
	// pageInUse位图扫描多少页。由页面回收器使用。
	// 
	// 更高的值可以减少扫描索引上的争用（例如
	// h.ReclaundIndex），但会增加
	// 操作的最小延迟。
	// 
	// 扫描这么多页面所需的时间可能会有很大差异，具体取决于实际释放的跨距数。在实验上，它可以在2.6GHz的核心i7上以大约300GB/ms的速度扫描页面，但只能以大约32MB/ms的速度扫描页面。使用512个页面将其限制在大约100µs。
	// 
	// 必须是pageInUse位图元素大小的倍数，
	// 还必须均匀划分页面大小。
	pagesPerReclaimerChunk = 512

	// physPageAlignedStacks表示堆栈分配是否必须与物理页面对齐。这是
	// OpenBSD上的MAP_堆栈的要求。
	physPageAlignedStacks = GOOS == "openbsd"
)

// 主malloc堆。
// 堆本身是“自由”和“scav”的treap，
// 但所有其他全局数据也在这里。
// 
// mheap不能被堆分配，因为它包含mSpanLists，
// 不能被堆分配。
// 
// go:notinheap 
type mheap struct {
	// 必须仅在系统堆栈上获取锁，否则，如果g 
	// 的堆栈随着锁的保持而增长，可能会自锁死。
	lock  mutex
	pages pageAlloc // 页面分配数据结构

	sweepgen uint32 // 扫描生成，请参见mspan中的注释；写在STW 

	// allspans是有史以来所有MSPAN的一部分。每个mspan 
	// 只出现一次。
	// 
	// allspans的内存是手动管理的，可以被
	// 重新分配并随着堆的增长而移动。
	// 
	// 通常情况下，所有跨度均受mheap_232;保护。锁，它阻止并发访问并释放支持的
	// 存储。STW期间的访问可能不会持有锁，但
	// 必须确保分配不会发生在
	// 访问周围（因为这可能会释放备份存储）。
	allspans []*mspan // 所有跨度

	// uint32 

	// 比例扫描
	// 
	// 这些参数表示gcController的线性函数。heapLive 
	// 用于页面扫描计数。比例扫描系统将当前页面扫描计数
	// 保持在黑色。希普利夫。
	// 保持在当前gcController的这一行之上，从而使
	// 
	// 该线具有斜率扫掠PageSperByte，并在（SweegheaplieveBasis，pagesSweptBasis）处通过一个
	// 基点。在任何给定的时间，系统都位于（gcController.heapLive，
	// pagesSwept）这个空间。
	// 
	// 线路通过我们控制的点，而不是简单地从0,0原点开始，这一点很重要
	// 因为这让我们可以随时调整扫描速度，同时
	// 考虑当前进度。如果我们只能调整
	// 斜率，如果任何
	// 已经取得进展，这将造成债务的不连续性。
	pagesInUse         atomic.Uint64 // 统计数据中的跨距页面使用
	pagesSwept         atomic.Uint64 // 本周期扫描的页面
	pagesSweptBasis    atomic.Uint64 // 页面扫描用作gcController扫描比率的原点
	sweepHeapLiveBasis uint64        // 值。heapLive用作扫描比的原点；带锁写入，不带
	sweepPagesPerByte  float64       // 比例扫描比；带锁写入，不带
	// TODO读取（奥斯汀）：pagesInUse应该是一个uintptr，但386 
	// 编译器不能对8字节字段进行对齐。

	// Cleaventegoal是保留的堆内存总量（用
	// hepretated），运行时将通过向操作系统返回内存
	// 来尝试维护。
	// 
	// 以原子方式访问。
	scavengeGoal uint64

	// 页面回收器状态

	// 回收索引是
	// 回收的下一页的allArenas中的页面索引。具体来说，它指的是arena allArenas[i/pagesPerArena]的页面（i%
	// pagesPerArena）。
	// 
	// 如果大于等于1<<63，页面回收器将扫描页面标记。
	reclaimIndex atomic.Uint64

	// RecoverCredit是额外扫描页面的备用信用。由于
	// 页面回收器工作在大数据块中，因此它可能会回收超出请求的
	// 页面。任何发布的备用页面都可以进入
	// 信用池。
	reclaimCredit atomic.Uintptr

	// 竞技场是堆竞技场地图。它指向
	// 的元数据，即整个可用虚拟
	// 地址空间的每个竞技场帧的堆。
	// 
	// 使用arenaIndex将索引计算到此数组中。
	// 
	// 对于地址空间中没有
	// Go堆支持的区域，竞技场地图包含零。
	// 
	// 修改受mheap保护。锁读取可以是
	// 执行，无需锁定；但是，当锁
	// 未被持有时，给定条目可以随时从nil转换为非nil。（条目从不转换回零。）
	// 
	// 一般来说，这是一个两级映射，由一个L1 
	// 映射和许多L2映射组成。当有大量的竞技场框架时，这样可以节省空间。然而，在许多
	// 平台（甚至64位）上，arenaL1Bits是0，这使得这个
	// 实际上是一个单级映射。在这种情况下，arenas[0]
	// 将永远不会为零。
	arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena

	// HEAPRENAALLOC是为分配HEAPRENA 
	// 对象而预先保留的空间。这只在32位上使用，我们预先保留了
	// 这个空间，以避免它与堆本身交错。
	heapArenaAlloc linearAlloc

	// arenaHints是试图添加更多堆竞技场的地址列表。它最初由一组
	// 通用提示地址填充，并随着
	// 实际堆范围的边界而增长。
	arenaHints *arenaHint

	// arena是一个预先保留的空间，用于分配堆竞技场
	// （实际竞技场）。这仅用于32位。
	arena linearAlloc

	// allArenas是所有地图竞技场的竞技场索引。这可以用来遍历地址空间。
	// 
	// 访问受mheap保护。锁然而，由于这是
	// 仅追加，而且旧的备份阵列永远不会被释放，因此获取mheap_u979;是安全的。锁定、复制切片头，并
	// 然后释放mheap_10;。锁
	allArenas []arenaIdx

	// sweepArenas是在
	// 扫描周期开始时拍摄的allArenas的快照。这可以通过
	// 简单地阻止GC（通过禁用抢占）安全地读取。
	sweepArenas []arenaIdx

	// markArenas是在标记周期开始时拍摄的allArenas的快照。因为allArenas是append only，所以在标记过程中，这个片段及其内容都不会改变，所以可以安全地读取。
	markArenas []arenaIdx

	// curArena是heap目前正在发展的竞技场。这应该始终与physPageSize对齐。
	curArena struct {
		base, end uintptr
	}

	_ uint32 // 确保中心

	// 小型类的中心自由列表的64位对齐。
	// 填充确保mcentral是
	// 将CacheLinePadSize字节隔开，以便每个mcentral。锁
	// 获取自己的缓存线。
	// central按span类编制索引。
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}

	spanalloc             fixalloc // span*
	cachealloc            fixalloc // mcache的分配器*
	specialfinalizeralloc fixalloc // specialfinalizer的分配器*
	specialprofilealloc   fixalloc // specialprofile的分配器*
	specialReachableAlloc fixalloc // specialReachable 
	speciallock           mutex    // special record分配器的锁。
	arenaHintAlloc        fixalloc // arenaHints的分配器

	unused *specialfinalizer // 从未设置，只是在这里强制将特殊finalizer类型设置为DWARF 
}

var mheap_ mheap

// heaperna存储堆竞技场的元数据。heapArenas存储在Go堆之外的
// 中，并通过mheap\ux进行访问。竞技场指数。
// 
// go:notinheap 
type heapArena struct {
	// 位图存储
	// /这个竞技场。见mbitmap。去描述一下。使用
	// heapBits类型访问此文件。
	bitmap [heapArenaBitmapBytes]byte

	// 将此竞技场中的虚拟地址页ID映射到*mspan。
	// 对于分配的跨距，它们的页面映射到跨距本身。
	// 对于自由跨距，只有最低和最高页面映射到跨距本身。
	// 内部页面映射到任意跨度。
	// 对于从未分配的页面，spans条目为零。
	// 
	// 修改受mheap保护。锁可以在不锁定的情况下执行
	// 读取，但只能从已知包含正在使用或堆栈跨度的
	// 索引执行读取。这意味着在确定
	// 地址是活动地址和在spans数组中查找地址之间，一定不能有一个安全点。
	spans [pagesPerArena]*mspan

	// pageInUse是一个位图，指示哪些跨距在
	// state mSpanInUse中。此位图按页码
	// 编制索引，但仅使用每个
	// span中第一页对应的位。
	// 
	// 读写是原子的。
	pageInUse [pagesPerArena / 8]uint8

	// pageMarks是一个位图，指示哪些跨距上有任何标记的对象。与pageInUse一样，只使用与每个跨距中的第一页对应的位
	// 。
	// 
	// 在标记期间以原子方式完成写入操作。读取是
	// 非原子且无锁的，因为它们只在
	// 扫描期间发生（因此从不与写入竞争）。
	// 
	// 用于快速查找可以释放的整个跨度。
	// 
	// TODO（奥斯汀）：如果这是uint64用于
	// 更快的扫描，那就太好了，但我们没有64位原子位
	// 操作。
	pageMarks [pagesPerArena / 8]uint8

	// pageSpecials是一个位图，指示哪些跨区有
	// specials（终结器或其他）。与pageInUse一样，只使用与每个跨距中的第一页对应的位
	// 。
	// 
	// 每当一个特殊项被添加到一个span中，以及当最后一个特殊项从span中删除时，都会以原子方式执行写入操作。
	// 在标记过程中，以原子方式进行读取，以查找包含特殊符号
	// 的跨距。
	pageSpecials [pagesPerArena / 8]uint8

	// 复选标记存储调试。gccheckmark状态。只有
	// 在调试时才使用。gccheckmark>0。
	checkmarks *checkmarksMap

	// zeroedBase标记此
	// arena中第一页的第一个字节，该字节尚未使用，因此已为
	// zero。zeroedBase是相对于竞技场基座的。
	// 单调增加，直到达到峰值。
	// 
	// 此字段足以确定分配
	// 是否需要归零，因为页面分配器遵循
	// 地址顺序优先适应策略。
	// 
	// 原子读写。
	zeroedBase uintptr
}

// arenaHint是一个关于堆竞技场发展方向的提示。见
// mheap_u2;。阿雷纳欣茨。
// 
// go:notinheap 
type arenaHint struct {
	addr uintptr
	down bool
	next *arenaHint
}

// mspan是一个页面的运行。
// 
// 当mspan处于无堆状态时，状态==mSpanFree 
// 和heapmap（s->start）=span，heapmap（s->start+s->npages-1）=span。
// 如果mspan在堆scav treap中，那么除了上面的
// 之外，清除==true。清除==在所有其他情况下为假。
// 
// 分配mspan时，状态==mSpanInUse或mSpanManual 
// 和heapmap（i）==span for all s->start<=i<s->start+s->npages。

// 每个mspan都在一个双链接列表中，或者在mheap的
// 忙列表中，或者在McCentral的一个span列表中。

// 表示实际内存的mspan具有状态mSpanInUse、
// mSpanManual或mSpanFree。这些状态之间的转换受到如下限制：在任何GC 
// /阶段，跨度可以从自由转换为使用中或手动。
// 
// *在扫描过程中（gcphase==_GCoff），一个跨度可能会从
// 正在使用到释放（由于扫描）或手动到释放（由于
// 堆栈被释放）。
// 
// *在GC（gcphase！=\u GCoff）期间，span*不得*从
// 手动或正在使用以释放。由于并发GC可能会读取指针
// 然后查找其范围，因此范围状态必须是单调的。
// 
// 设置mspan。mSpanInUse或MSPANMANULY的状态必须以原子方式进行
// 并且只有在所有其他span字段都有效之后。
// 同样，如果检查跨度取决于它是否为
// mSpanInUse，则应以原子方式加载状态，并在依赖其他字段之前检查
// 状态。这允许垃圾收集器
// 安全地处理可能无效的指针，因为解析
// 这样的指针可能会与分配的范围竞争。
type mSpanState uint8

const (
	mSpanDead   mSpanState = iota
	mSpanInUse             // 已分配给垃圾收集堆
	mSpanManual            // 已分配给手动管理（例如堆栈分配器）
)

// mSpanStateNames是由
// mSpanState索引的范围状态的名称。
var mSpanStateNames = []string{
	"mSpanDead",
	"mSpanInUse",
	"mSpanManual",
	"mSpanFree",
}

// mSpanStateBox包含一个mSpanState，并在其上提供原子操作。这是一个单独的类型，不允许与mSpanState进行意外比较或
// 赋值。
type mSpanStateBox struct {
	s mSpanState
}

func (b *mSpanStateBox) set(s mSpanState) {
	atomic.Store8((*uint8)(&b.s), uint8(s))
}

func (b *mSpanStateBox) get() mSpanState {
	return mSpanState(atomic.Load8((*uint8)(&b.s)))
}

// mSpanList是一个跨度链接列表的标题。
// 
// go:notinheap 
type mSpanList struct {
	first *mspan // 列表中的第一个span，如果没有
	last  *mspan // 列表中的最后一个span，则为零；如果没有
}

// go:notinheap 
type mspan struct {
	next *mspan     // 列表中的下一个span，则为零；如果没有
	list *mSpanList // 则为零，用于调试。TODO:移除。

	startAddr uintptr // span aka s.base（）的第一个字节的地址
	npages    uintptr // span中的页数

	manualFreeList gclinkptr // mSpanManual中的自由对象列表

	// 自由索引是0和nelems之间的插槽索引，在该索引处开始扫描
	// 以查找该跨度中的下一个自由对象。
	// 每个分配从freeindex开始扫描allocBits，直到遇到一个0 
	// 表示一个空闲对象。然后调整freeindex，以便后续扫描开始
	// 刚刚经过新发现的自由对象。
	// 
	// 如果freeindex==nelem，则此跨度没有自由对象。
	// 
	// allocBits是此范围内对象的位图。
	// 如果n>=freeindex和allocBits[n/8]&（1<<（n%8））为0 
	// 则对象n是自由的；
	// 否则，分配对象n。从nelem开始的位是
	// 未定义的，不应被引用。
	// 
	// 对象n从地址n*elemsize+开始（开始<<pageShift）。
	freeindex uintptr
	// TODO:从sizeclass中查找nelems，如果它有助于提高性能，请删除此字段。
	nelems uintptr // 范围内的对象数。

	// 在freeindex上的allocBits缓存。allocCache被移位为
	// 以便最低的位对应于位自由索引。
	// allocCache保留allocBits的补码，因此允许
	// ctz（计数尾随零）直接使用它。
	// allocache可能包含s.nelems以外的位；呼叫方必须忽略
	// 这些。
	allocCache uint64

	// allocBits和gcmarkBits保存指向跨距标记的指针，
	// 分配位。指针是8字节对齐的。
	// 保存这些数据的领域有三个。
	// 免费：不再访问的脏竞技场
	// 可以重复使用。
	// next:保存下一个GC循环中使用的信息。
	// 当前：此GC循环中使用的信息。
	// previous:上一个GC循环中使用的信息。
	// 一个新的GC循环从调用finishsweep_m开始。
	// finishsweep_m将前一个竞技场移到自由竞技场，
	// 将当前竞技场移到前一个竞技场，
	// 将下一个竞技场移到当前竞技场。
	// 当跨距请求
	// 内存为下一个GC循环保存gcmarkBits，以及
	// 作为新分配跨距的allocBits时，下一个竞技场被填充。
	// 
	// 指针运算是“手动”完成的，而不是使用
	// 数组来避免关键性能路径上的边界检查。
	// 扫描将释放旧的allocBits并将allocBits设置为
	// gcmarkBits。gcmarkBits被一个新的归零
	// out内存替换。
	allocBits  *gcBits
	gcmarkBits *gcBits

	// 扫描生成：
	// 如果sweepgen==h->sweepgen-2，则跨度需要扫描
	// 如果sweepgen==h->sweepgen-1，则当前正在扫描范围
	// 如果sweepgen==h->sweepgen，则扫描范围并准备好使用
	// 如果sweepgen==h->sweepgen+1，则扫描开始前已缓存该范围，并且仍在缓存中，需要扫描
	// 如果sweepgen==h->sweepgen+3，span被扫描，然后被缓存，并且仍然被缓存
	// h->sweepgen在每次GC 

	sweepgen    uint32
	divMul      uint32        // for除以elemsize 
	allocCount  uint16        // 分配的对象数
	spanclass   spanClass     // size class和noscan（uint8）
	state       mSpanStateBox // mSpanInUse等之后增加2；原子访问（get/set方法）
	needzero    uint8         // 分配前需要归零
	elemsize    uintptr       // 从sizeclass或npages 
	limit       uintptr       // span 
	speciallock mutex         // guards specials list 
	specials    *special      // 按偏移量排序的特殊记录链接列表。
}

func (s *mspan) base() uintptr {
	return s.startAddr
}

func (s *mspan) layout() (size, n, total uintptr) {
	total = s.npages << _PageShift
	size = s.elemsize
	if size > 0 {
		n = total / size
	}
	return
}

// recordspan将新分配的span添加到h.allspan。
// 
// 这只在第一次从
// mheap分配范围时发生。Spanaloc（重复使用一个span时不调用它）。
// 
// 此处不允许使用写屏障，因为在分配新工作区时，可以从
// gcWork调用它。然而，由于它是来自fixalloc初始值设定项的
// 间接调用，编译器无法看到
// 这一点。
// 
// 必须持有堆锁。
// 
// go:nowritebarrierrec 
func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
	h := (*mheap)(vh)
	s := (*mspan)(p)

	assertLockHeld(&h.lock)

	if len(h.allspans) >= cap(h.allspans) {
		n := 64 * 1024 / goarch.PtrSize
		if n < cap(h.allspans)*3/2 {
			n = cap(h.allspans) * 3 / 2
		}
		var new []*mspan
		sp := (*slice)(unsafe.Pointer(&new))
		sp.array = sysAlloc(uintptr(n)*goarch.PtrSize, &memstats.other_sys)
		if sp.array == nil {
			throw("runtime: cannot allocate memory")
		}
		sp.len = len(h.allspans)
		sp.cap = n
		if len(h.allspans) > 0 {
			copy(new, h.allspans)
		}
		oldAllspans := h.allspans
		*(*notInHeapSlice)(unsafe.Pointer(&h.allspans)) = *(*notInHeapSlice)(unsafe.Pointer(&new))
		if len(oldAllspans) != 0 {
			sysFree(unsafe.Pointer(&oldAllspans[0]), uintptr(cap(oldAllspans))*unsafe.Sizeof(oldAllspans[0]), &memstats.other_sys)
		}
	}
	h.allspans = h.allspans[:len(h.allspans)+1]
	h.allspans[len(h.allspans)-1] = s
}

// A spanClass代表一个span的大小级别和无扫描性。
// 
// 每个尺寸类别都有一个noscan Span类别和一个scan Span类别。
// noscan span类只包含noscan对象，这些对象不包含
// 指针，因此不需要垃圾
// 收集器进行扫描。
type spanClass uint8

const (
	numSpanClasses = _NumSizeClasses << 1
	tinySpanClass  = spanClass(tinySizeClass<<1 | 1)
)

func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
	return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}

func (sc spanClass) sizeclass() int8 {
	return int8(sc >> 1)
}

func (sc spanClass) noscan() bool {
	return sc&1 != 0
}

// arenaIndex将索引返回到mheap_。竞技场的竞技场
// 包含p的元数据。该索引将一个索引合并到
// L1地图和一个索引合并到L2地图中，应该用作
// mheap_2;。竞技场[ai.l1（）][ai.l2（）]。
// 
// 如果p超出有效堆地址的范围，则l1（）或
// l2（）都将超出范围。
// 
// 它是nosplit，因为它由spanOf和其他几个
// nosplit函数调用。
// 
// go:nosplit 
func arenaIndex(p uintptr) arenaIdx {
	return arenaIdx((p - arenaBaseOffset) / heapArenaBytes)
}

// arenaBase返回堆
// arena i所覆盖区域的低位地址。
func arenaBase(i arenaIdx) uintptr {
	return uintptr(i)*heapArenaBytes + arenaBaseOffset
}

type arenaIdx uint

func (i arenaIdx) l1() uint {
	if arenaL1Bits == 0 {
		// 如果没有
		// L1映射，让编译器对其进行优化。
		return 0
	} else {
		return uint(i) >> arenaL1Shift
	}
}

func (i arenaIdx) l2() uint {
	if arenaL1Bits == 0 {
		return uint(i)
	} else {
		return uint(i) & (1<<arenaL2Bits - 1)
	}
}

// inheap报告b是否是指向（可能已死亡）堆对象的指针。
// 对于指向mSpanManual跨度的指针，返回false。
// 不可抢占，因为它被写屏障使用。
// go:nowritebarrier 
// go:nosplit 
func inheap(b uintptr) bool {
	return spanOfHeap(b) != nil
}

// inHeapOrStack是inheap的一个变体，对于任何分配的堆跨度中的指针返回true。
// 
// go:nowritebarrier 
// go:nosplit 
func inHeapOrStack(b uintptr) bool {
	s := spanOf(b)
	if s == nil || b < s.base() {
		return false
	}
	switch s.state.get() {
	case mSpanInUse, mSpanManual:
		return b < s.limit
	default:
		return false
	}
}

// spanOf返回p的跨度。如果p没有指向堆
// arena或没有跨度包含p，spanOf返回零。
// 
// 如果p不指向已分配内存，则可能返回一个不包含p的非零
// span。如果可能，
// 调用者应该调用SPANOHEAP或显式检查span界限
// 。
// 
// 必须是nosplit，因为它的调用者是nosplit。
// 
// go:nosplit 
func spanOf(p uintptr) *mspan {
	// 这个函数看起来很大，但我们使用了很多常量
	// 围绕arenaL1Bits进行折叠，以将其放入内嵌的
	// 预算中。此外，这里的许多检查都是安全检查，Go无论如何都需要做，所以生成的代码非常简短。
	ri := arenaIndex(p)
	if arenaL1Bits == 0 {
		// 如果没有L1，那么ri。l1（）不能超出范围，但ri除外。l2（）可以。
		if ri.l2() >= uint(len(mheap_.arenas[0])) {
			return nil
		}
	} else {
		// 如果有L1，则为ri。l1（）可能超出范围，但ri。l2（）不能。如果没有L1，就永远不会发生。
		if ri.l1() >= uint(len(mheap_.arenas)) {
			return nil
		}
	}
	l2 := mheap_.arenas[ri.l1()]
		return nil
	}
	ha := l2[ri.l2()]
	if ha == nil {
		return nil
	}
	return ha.spans[(p/pageSize)%pagesPerArena]
}

// spanOfUnchecked等同于spanOf，但调用方必须确保p指向分配的堆。
// 
// 必须是nosplit，因为它的调用者是nosplit。
// 
// go:nosplit 
func spanOfUnchecked(p uintptr) *mspan {
	ai := arenaIndex(p)
	return mheap_.arenas[ai.l1()][ai.l2()].spans[(p/pageSize)%pagesPerArena]
}

// spanOfHeap类似于spanOf，但如果p不指向
// 堆对象。
// 
// 必须是nosplit，因为它的调用者是nosplit。
// 
// go:nosplit 
func spanOfHeap(p uintptr) *mspan {
	s := spanOf(p)
	// s如果从未分配过，则为零。否则，我们首先检查
	// 它的状态，因为我们不信任这个指针，所以我们必须与span初始化同步。然后是
	// 仍然有可能我们找到了一个过时的span指针，所以我们必须检查span的边界。
	if s == nil || s.state.get() != mSpanInUse || p < s.base() || p >= s.limit {
		return nil
	}
	return s
}

// pageIndexOf返回指针p的区域、页面索引和页面掩码。
// 调用者必须确保p在堆中。
func pageIndexOf(p uintptr) (arena *heapArena, pageIdx uintptr, pageMask uint8) {
	ai := arenaIndex(p)
	arena = mheap_.arenas[ai.l1()][ai.l2()]
	pageIdx = ((p / pageSize) / 8) % uintptr(len(arena.pageInUse))
	pageMask = byte(1 << ((p / pageSize) % 8))
	return
}

// 初始化堆。
func (h *mheap) init() {
	lockInit(&h.lock, lockRankMheap)
	lockInit(&h.speciallock, lockRankMheapSpecial)

	h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
	h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys)
	h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
	h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
	h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys)
	h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys)

	// 不要将mspan分配归零。背景扫描可以
	// 在分配范围的同时检查范围，因此，在释放
	// 并重新分配范围以防止背景扫描
	// 从0不正确地搜索范围时，范围的扫描基因存活下来是非常重要的。
	// 
	// 这是安全的，因为mspan不包含堆指针。
	h.spanalloc.zero = false

	// h->mapcache不需要初始化

	for i := range h.central {
		h.central[i].mcentral.init(spanClass(i))
	}

	h.pages.init(&h.lock, &memstats.gcMiscSys)
}

// Recall扫描并将至少一页页回收到堆中。
// 在分配npage页面之前调用它，以控制增长。
// 
// Recall实现了页面回收器的一半。不能持有锁。
func (h *mheap) reclaim(npage uintptr) {
	// TODO（austin）：释放跨距的时间有一半是在锁定/解锁堆（即使争用较低）。我们
	// 通过
	// 批处理堆释放，可以使这里的慢路径快几倍。

	// 如果没有更多的回收工作，请提前保释。
	if h.reclaimIndex.Load() >= 1<<63 {
		return
	}

	// 禁用抢占，这样GC就不能在我们进行扫描时启动，这样我们就可以读取h.sweepArenas，所以
	// 在P.
	mp := acquirem()

	if trace.enabled {
		traceGCSweepStart()
	}

	arenas := h.sweepArenas
	locked := false
	for npage > 0 {
		// 首先从累积信用中提取。只带我们需要的东西。
		if credit := h.reclaimCredit.Load(); credit > 0 {
			take := credit
			if take > npage {
				take = npage
			}
			if h.reclaimCredit.CompareAndSwap(credit, credit-take) {
				npage -= take
			}
			continue
		}

		// 索取大量工作。
		idx := uintptr(h.reclaimIndex.Add(pagesPerReclaimerChunk) - pagesPerReclaimerChunk)
		if idx/pagesPerArena >= uintptr(len(arenas)) {
			// 页面回收完成。
			h.reclaimIndex.Store(1 << 63)
			break
		}

		if !locked {
			// 锁定堆以回收块。
			lock(&h.lock)
			locked = true
		}

		// 扫描此区块。
		nfound := h.reclaimChunk(arenas, idx, pagesPerReclaimerChunk)
		if nfound <= npage {
			npage -= nfound
		} else {
			// 将多余的页面放入全球信贷。
			h.reclaimCredit.Add(nfound - npage)
			npage = 0
		}
	}
	if locked {
		unlock(&h.lock)
	}

	if trace.enabled {
		traceGCSweepDone()
	}
	releasem(mp)
}

// 回收从页面索引开始的未标记范围[pageIdx，pageIdx+n）.
// 它返回返回到堆的页数。
// 
// 必须保持h.锁，并且调用方必须是不可抢占的。注意：h.锁可能是
// 临时解锁并重新锁定，以便进行扫描，或者如果跟踪是
// 启用的。
func (h *mheap) reclaimChunk(arenas []arenaIdx, pageIdx, n uintptr) uintptr {
	// 必须保持堆锁，因为is访问
	// heapArena。使用潜在的非活动指针跨越数组。
	// 特别是，如果一个跨度被释放并与这个探测heapArena同时合并。span，可以观察任意的、陈旧的span指针。
	assertLockHeld(&h.lock)

	n0 := n
	var nFreed uintptr
	sl := sweep.active.begin()
	if !sl.valid {
		return 0
	}
	for n > 0 {
		ai := arenas[pageIdx/pagesPerArena]
		ha := h.arenas[ai.l1()][ai.l2()]

		// 获取一块位图进行处理。
		arenaPage := uint(pageIdx % pagesPerArena)
		inUse := ha.pageInUse[arenaPage/8:]
		marked := ha.pageMarks[arenaPage/8:]
		if uintptr(len(inUse)) > n/8 {
			inUse = inUse[:n/8]
			marked = marked[:n/8]
		}

		// 扫描此位图块以查找正在使用的跨距
		// 但上面没有标记的对象。wen jian defg
		for i := range inUse {
			inUseUnmarked := atomic.Load8(&inUse[i]) &^ marked[i]
			if inUseUnmarked == 0 {
				continue
			}

			for j := uint(0); j < 8; j++ {
				if inUseUnmarked&(1<<j) != 0 {
					s := ha.spans[arenaPage+uint(i)*8+j]
					if s, ok := sl.tryAcquire(s); ok {
						npages := s.npages
						unlock(&h.lock)
						if s.sweep(false) {
							nFreed += npages
						}
						lock(&h.lock)
						// 当我们丢弃
						// 锁时，spans被释放了，我们不想从spans数组中得到过时的
						// 指针。
						inUseUnmarked = atomic.Load8(&inUse[i]) &^ marked[i]
					}
				}
			}
		}

		// 前进。
		pageIdx += uintptr(len(inUse) * 8)
		n -= uintptr(len(inUse) * 8)
	}
	sweep.active.end(sl)
	if trace.enabled {
		unlock(&h.lock)
		// 已扫描但未回收的页面的帐户。
		traceGCSweepSpan((n0 - nFreed) * pageSize)
		lock(&h.lock)
	}

	assertLockHeld(&h.lock) // 返回时必须锁定。
	return nFreed
}

// spanAllocType表示要进行的分配类型，或
// 要释放的分配类型。
type spanAllocType uint8

const (
	spanAllocHeap          spanAllocType = iota // 堆span 
	spanAllocStack                              // 堆栈span 
	spanAllocPtrScalarBits                      // 展开的GC程序位图span 
	spanAllocWorkBuf                            // 工作buf span 
)

// 如果手动管理跨度分配，则手动返回true。
func (s spanAllocType) manual() bool {
	return s != spanAllocHeap
}

// alloc从GC的堆中分配新的npage页跨度。
// 
// spanclass表示跨度的大小等级和可扫描性。
// 
// 返回已完全初始化的范围。跨度needzero表示
// 量程是否已归零。请注意，它可能不是。
func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan {
	// 不要执行任何锁定G堆栈上堆的操作。
	// 可能会触发堆栈增长，堆栈增长代码需要
	// 才能分配堆。
	var s *mspan
	systemstack(func() {
		// 为了防止堆的过度增长，在分配n个页面之前
		// 我们需要扫描和回收至少n个页面。
		if !isSweepDone() {
			h.reclaim(npages)
		}
		s = h.allocSpan(npages, spanAllocHeap, spanclass)
	})
	return s
}

// allocManual分配手动管理的npage页面范围。
// 如果分配失败，allocManual返回零。
// 
// allocManual添加用于*stat的字节，该字节应为
// memstats in use字段。与GC'd堆中的分配不同，
// 分配*不*计入heap_inuse或heap_sys。
// 
// 如果
// span，则支持返回span的内存可能不会归零。设置了零。
// 
// 必须在系统堆栈上调用allocManual，因为它可能通过AllocPan获取堆锁。详见mheap。
// 
// 如果编写了新代码来调用allocManual，请不要使用
// 现有的spanAllocType值，而是声明一个新值。
// 
// go:systemstack 
func (h *mheap) allocManual(npages uintptr, typ spanAllocType) *mspan {
	if !typ.manual() {
		throw("manual span allocation called with non-manually-managed type")
	}
	return h.allocSpan(npages, typ, 0)
}

// setSpans修改跨度映射，使[spanOf（base），spanOf（base+npage*pageSize））
// s.
func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
	p := base / pageSize
	ai := arenaIndex(base)
	ha := h.arenas[ai.l1()][ai.l2()]
	for n := uintptr(0); n < npage; n++ {
		i := (p + n) % pagesPerArena
		if i == 0 {
			ai = arenaIndex(base + n*pageSize)
			ha = h.arenas[ai.l1()][ai.l2()]
		}
		ha.spans[i] = s
	}
}

// allocNeedsZero检查地址空间的区域[base，base+npage*pageSize]、
// 假设要分配，需要归零，为
// 未来的分配更新堆竞技场元数据。
// 
// 每次从堆中分配页时都必须调用此函数，即使页面
// 否则，分配器可以证明其分配的内存已经为零，因为
// 它们刚从操作系统中出来。它更新了对未来页面分配至关重要的heapArena元数据。
// 
// 此方法没有锁定约束。
func (h *mheap) allocNeedsZero(base, npage uintptr) (needZero bool) {
	for npage > 0 {
		ai := arenaIndex(base)
		ha := h.arenas[ai.l1()][ai.l2()]

		zeroedBase := atomic.Loaduintptr(&ha.zeroedBase)
		arenaBase := base % heapArenaBytes
		if arenaBase < zeroedBase {
			// 我们扩展到了
			// 竞技场的非归零部分，所以这个区域在使用前需要归零。
			// 
			// zeroedBase是单调递增的，所以如果我们现在看到这个，那么我们可以确定我们需要将这个内存区域归零。
			// 
			// 我们仍然需要更新这个竞技场的zeroedBase，
			// 可能还有更多竞技场。
			needZero = true
		}
		// 如果我们与地址
		// 分配竞争，我们可能会观察到arenaBase>zeroedBase。但是，因为我们知道没有其他人在获取这个内存，所以
		// 空间中直接在我们前面获取内存的一个或多个
		// 仍然安全到不为零。

		// 计算我们延伸到竞技场的距离，上限
		// 在重晶界。
		arenaLimit := arenaBase + npage*pageSize
		if arenaLimit > heapArenaBytes {
			arenaLimit = heapArenaBytes
		}
		// 增加ha。所以它是>=arenaLimit。
		// 我们可能正在与其他更新竞争。
		for arenaLimit > zeroedBase {
			if atomic.Casuintptr(&ha.zeroedBase, zeroedBase, arenaLimit) {
				break
			}
			zeroedBase = atomic.Loaduintptr(&ha.zeroedBase)
			// 仔细检查zeroedBase的基本条件。
			if zeroedBase <= arenaLimit && zeroedBase > arenaBase {
				// zeroedBase移动到了我们试图声明的空间。这很糟糕，表明有人分配了
				// 与我们分配的区域相同。
				throw("potentially overlapping in-use allocations detected")
			}
		}

		// 向前移动基地并从npage中减去，进入下一个竞技场，或完成。
		base += arenaLimit - arenaBase
		npage -= (arenaLimit - arenaBase) / pageSize
	}
	return
}

// tryAllocMSpan尝试从
// /P-local缓存分配mspan对象，但可能会失败。
// 
// h.锁不需要持有。
// 
// 此调用方必须确保其P在
// 下不会发生变化。目前，为了确保我们强制执行
// 该函数在系统堆栈上运行，因为这是
// 现在唯一使用它的地方。将来，如果有必要在其他地方使用
// 的话，这一要求可能会放宽。
// 
// go:systemstack 
func (h *mheap) tryAllocMSpan() *mspan {
	pp := getg().m.p.ptr()
	// 如果我们没有p或者缓存是空的，我们不能在这里执行任何操作。
	if pp == nil || pp.mspancache.len == 0 {
		return nil
	}
	// 删除缓存中的最后一个条目。
	s := pp.mspancache.buf[pp.mspancache.len-1]
	pp.mspancache.len--
	return s
}

// allocMSpanLocked分配一个mspan对象。
// 
// h.锁必须保持。
// 
// 必须在系统堆栈上调用allocMSpanLocked，因为
// 其调用者持有堆锁。详见mheap。
// 在系统堆栈上运行也可以确保我们不会在该功能期间切换Ps。有关详细信息，请参见tryAllocMSpan。
// 
// go:systemstack 
func (h *mheap) allocMSpanLocked() *mspan {
	assertLockHeld(&h.lock)

	pp := getg().m.p.ptr()
	if pp == nil {
		// 我们没有p，所以做正常的事情。
		return (*mspan)(h.spanalloc.alloc())
	}
	// 必要时重新填充缓存。
	if pp.mspancache.len == 0 {
		const refillCount = len(pp.mspancache.buf) / 2
		for i := 0; i < refillCount; i++ {
			pp.mspancache.buf[i] = (*mspan)(h.spanalloc.alloc())
		}
		pp.mspancache.len = refillCount
	}
	// 提取缓存中的最后一个条目。
	s := pp.mspancache.buf[pp.mspancache.len-1]
	pp.mspancache.len--
	return s
}

// freeMSpanLocked释放一个mspan对象。
// 
// h.锁必须保持。
// 
// 必须在系统堆栈上调用freeMSpanLocked，因为
// 其调用者持有堆锁。详见mheap。
// 在系统堆栈上运行也确保了我们不会在这个函数期间切换Ps。有关详细信息，请参见tryAllocMSpan。
// 
// go:systemstack 
func (h *mheap) freeMSpanLocked(s *mspan) {
	assertLockHeld(&h.lock)

	pp := getg().m.p.ptr()
	// 首先尝试将mspan直接释放到缓存中。
	if pp != nil && pp.mspancache.len < len(pp.mspancache.buf) {
		pp.mspancache.buf[pp.mspancache.len] = s
		pp.mspancache.len++
		return
	}
	// 如果失败（或者如果我们没有p），只需将其释放到堆中即可。
	h.spanalloc.free(unsafe.Pointer(s))
}

// allocSpan分配一个拥有相当于npages内存的mspan。
// 
// If类型。manual（）==false，allocSpan分配类spanclass 
// 的堆范围，并更新堆记帐。如果manual==true，AllocPan将分配一个
// 手动管理的span（忽略span类），调用者是
// 负责与其span使用相关的任何记帐。无论是
// 方式，allocSpan都会自动将新分配的
// span中的字节添加到*sysStat。
// 
// 返回的范围已完全初始化。
// 
// h.锁不能被持有。
// 
// 必须在系统堆栈上调用allocSpan，因为它获取了
// 堆锁，并且必须阻止GC转换。
// 
// go:systemstack 
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
	// 函数全局状态。
	gp := getg()
	base, scav := uintptr(0), uintptr(0)
	growth := uintptr(0)

	// 在某些平台上，我们需要提供物理页面对齐的堆栈
	// 分配。如果页面大小小于物理页面
	// size，我们已经在默认情况下做到了这一点。
	needPhysPageAlign := physPageAlignedStacks && typ == spanAllocStack && pageSize < physPageSize

	// 如果分配足够小，请尝试页面缓存！
	// 页面缓存不支持对齐分配，因此如果需要提供物理页面对齐堆栈分配，我们不能使用
	// 它。
	pp := gp.m.p.ptr()
	if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
		c := &pp.pcache

		// 如果缓存为空，请重新填充。
		if c.empty() {
			lock(&h.lock)
			*c = h.pages.allocToCache()
			unlock(&h.lock)
		}

		// 尝试从缓存进行分配。
		base, scav = c.alloc(npages)
		if base != 0 {
			s = h.tryAllocMSpan()
			if s != nil {
				goto HaveSpan
			}
			// 我们有一个基，但没有mspan，所以我们需要
			// 来锁定堆。
		}
	}

	// 出于这样或那样的原因，如果没有堆锁，我们无法完成整个工作。
	lock(&h.lock)

	if needPhysPageAlign {
		// 通过物理页面过度分配，以允许以后的对齐。
		npages += physPageSize / pageSize
	}

	if base == 0 {
		// 尝试获取基址。
		base, scav = h.pages.alloc(npages)
		if base == 0 {
			var ok bool
			growth, ok = h.grow(npages)
			if !ok {
				unlock(&h.lock)
				return nil
			}
			base, scav = h.pages.alloc(npages)
			if base == 0 {
				throw("grew heap, but no adequate free space found")
			}
		}
	}
	if s == nil {
		// 我们之前没有获得mspan，所以现在我们有了堆锁，就抓取一个。
		s = h.allocMSpanLocked()
	}

	if needPhysPageAlign {
		allocBase, allocPages := base, npages
		base = alignUp(allocBase, physPageSize)
		npages -= physPageSize / pageSize

		// 返回对齐分配周围的内存。
		spaceBefore := base - allocBase
		if spaceBefore > 0 {
			h.pages.free(allocBase, spaceBefore/pageSize, false)
		}
		spaceAfter := (allocPages-npages)*pageSize - spaceBefore
		if spaceAfter > 0 {
			h.pages.free(base+npages*pageSize, spaceAfter/pageSize, false)
		}
	}

	unlock(&h.lock)

	if growth > 0 {
		// 我们刚刚导致了堆的增长，所以清除即将使用的内容。
		// 通过清除内联，我们通过清除最不可能重复使用的内存片段来处理分配内存片段失败的问题。
		scavengeGoal := atomic.Load64(&h.scavengeGoal)
		if retained := heapRetained(); retained+uint64(growth) > scavengeGoal {
			// 清除算法要求丢弃堆锁，因此它只能少量获取堆锁。这是一个潜在的昂贵操作
			// 因此它同时释放了其他goroutine来分配。事实上，他们可以利用我们刚刚创造的增长。
			todo := growth
			if overage := uintptr(retained + uint64(growth) - scavengeGoal); todo > overage {
				todo = overage
			}
			h.pages.scavenge(todo)
		}
	}

HaveSpan:
	// 此时，两个s！=零和基地！=0，堆
	// 锁不再被持有。初始化跨度。
	s.init(base, npages)
	if h.allocNeedsZero(base, npages) {
		s.needzero = 1
	}
	nbytes := npages * pageSize
	if typ.manual() {
		s.manualFreeList = 0
		s.nelems = 0
		s.limit = s.base() + s.npages*pageSize
		s.state.set(mSpanManual)
	} else {
		// 我们必须在span发布到任何地方之前设置span属性
		// 因为我们没有持有堆锁。
		s.spanclass = spanclass
		if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
			s.elemsize = nbytes
			s.nelems = 1
			s.divMul = 0
		} else {
			s.elemsize = uintptr(class_to_size[sizeclass])
			s.nelems = nbytes / s.elemsize
			s.divMul = class_to_divmagic[sizeclass]
		}

		// 初始化标记和分配结构。
		s.freeindex = 0
		s.allocCache = ^uint64(0) // 所有1表示所有空闲。
		s.gcmarkBits = newMarkBits(s.nelems)
		s.allocBits = newAllocBits(s.nelems)

		// 在没有堆锁的情况下访问h.sweepgen是安全的，因为它是
		// 只在世界停止时更新，我们在
		// 系统堆栈上运行，该堆栈阻止STW转换。
		atomic.Store(&s.sweepgen, h.sweepgen)

		// 现在已经填写了跨度，请设置其状态。此
		// 是
		// 中其他字段的发布障碍。虽然进入该span的有效指针在返回span之前不应可见，但如果垃圾收集器发现无效指针，则对span的访问可能与对该span的初始化竞争。我们通过原子化
		// 在span完全初始化后设置状态
		// 并原子化检查
		// 中任何怀疑指针的情况下的状态来解决这场竞赛。
		s.state.set(mSpanInUse)
	}

	// 提交并说明span现在拥有的所有已清除内存。
	if scav != 0 {
		// 系统使用了范围内所有实际可用的页面，因为其中一些页面可能会被清除。
		sysUsed(unsafe.Pointer(base), nbytes)
		atomic.Xadd64(&memstats.heap_released, -int64(scav))
	}
	// 更新统计信息。
	if typ == spanAllocHeap {
		atomic.Xadd64(&memstats.heap_inuse, int64(nbytes))
	}
	if typ.manual() {
		// 手动管理的内存不计入heap_sys。
		memstats.heap_sys.add(-int64(nbytes))
	}
	// 更新一致的统计数据。
	stats := memstats.heapStats.acquire()
	atomic.Xaddint64(&stats.committed, int64(scav))
	atomic.Xaddint64(&stats.released, -int64(scav))
	switch typ {
	case spanAllocHeap:
		atomic.Xaddint64(&stats.inHeap, int64(nbytes))
	case spanAllocStack:
		atomic.Xaddint64(&stats.inStacks, int64(nbytes))
	case spanAllocPtrScalarBits:
		atomic.Xaddint64(&stats.inPtrScalarBits, int64(nbytes))
	case spanAllocWorkBuf:
		atomic.Xaddint64(&stats.inWorkBufs, int64(nbytes))
	}
	memstats.heapStats.release()

	// 在不同的位置发布span。

	// 在没有锁的情况下调用是安全的，因为与此跨距相关的插槽
	// 只能由
	// 此线程读取或修改，直到跨距中的指针被发布（在此之前，我们在函数
	// 的末尾执行一个发布屏障）或pageInUse被禁用更新。
	h.setSpans(s.base(), npages, s)

	if !typ.manual() {
		// 在竞技场页面位图中标记正在使用的范围。
		// 
		// 这会将跨距发布到页面清理程序，因此
		// 必须在此行之前完全初始化跨距。
		arena, pageIdx, pageMask := pageIndexOf(s.base())
		atomic.Or8(&arena.pageInUse[pageIdx], pageMask)

		// 更新相关页面清理器统计信息。
		h.pagesInUse.Add(int64(npages))
	}

	// 在发布指向范围的指针之前，确保GC将观察到新分配的范围。
	publicationBarrier()

	return s
}

// 尝试向堆中添加至少n个页面的内存，返回堆的增长量以及它是否工作。
// 
// h.锁必须保持。
func (h *mheap) grow(npage uintptr) (uintptr, bool) {
	assertLockHeld(&h.lock)

	// 我们必须把堆分成整个palloc块。
	// 我们在下面调用sysMap，但请注意，因为我们将pallocChunkPages集合到MiB的顺序
	// 上（通常>=到巨大的页面大小），所以
	// 不会调用太多。
	ask := alignUp(npage, pallocChunkPages) * pageSize

	totalGrowth := uintptr(0)
	// 这可能会溢出，因为ask可能非常大
	// 在其他方面与库拉雷纳病毒无关。基础
	end := h.curArena.base + ask
	nBase := alignUp(end, physPageSize)
	if nBase > h.curArena.end || /* overflow */ end < h.curArena.base {
		// 在目前的舞台上没有足够的空间。分配更多
		// 竞技场空间。这可能与
		// 当前竞技场不相邻，因此我们必须请求完整的ask。
		av, asize := h.sysAlloc(ask)
		if av == nil {
			print("runtime: out of memory: cannot allocate ", ask, "-byte block (", memstats.heap_sys, " in use)\n")
			return 0, false
		}

		if uintptr(av) == h.curArena.end {
			// 新空间与旧
			// 空间相邻，所以只需扩展当前空间即可。新空间不连续。跟踪当前空间的剩余内容，并切换到新空间。这应该是罕见的。
			h.curArena.end = uintptr(av) + asize
		} else {
			if size := h.curArena.end - h.curArena.base; size != 0 {
				// 将此空间从保留转换为已准备，并将其标记为已发布，因为我们可以在更新页面分配器并随时释放锁后开始使用它。
				sysMap(unsafe.Pointer(h.curArena.base), size, &memstats.heap_sys)
				// 更新统计信息。
				atomic.Xadd64(&memstats.heap_released, int64(size))
				stats := memstats.heapStats.acquire()
				atomic.Xaddint64(&stats.released, int64(size))
				memstats.heapStats.release()
				// 更新页面分配器的结构，使此
				// 空间准备好分配。
				h.pages.grow(h.curArena.base, size)
				totalGrowth += size
			}
			// 切换到新空间。
			h.curArena.base = uintptr(av)
			h.curArena.end = uintptr(av) + asize
		}

		// 重新计算nBase。
		// 我们知道这不会溢出，因为sysAlloc返回了
		// 一个从h.curArena开始的有效区域。基址为
		// 大小至少为ask字节。
		nBase = alignUp(h.curArena.base+ask, physPageSize)
	}

	// 成长为当前的竞技场。
	v := h.curArena.base
	h.curArena.base = nBase

	// 将我们要使用的空间从保留空间转换为准备好的空间。
	sysMap(unsafe.Pointer(v), nBase-v, &memstats.heap_sys)

	// 刚刚分配的内存同时计为已释放的
	// 和空闲的，尽管它还没有得到spans的支持。
	// 
	// 分配始终与堆竞技场
	// 大小始终>physPageSize，因此
	// 只需直接添加到堆中即可。
	atomic.Xadd64(&memstats.heap_released, int64(nBase-v))
	stats := memstats.heapStats.acquire()
	atomic.Xaddint64(&stats.released, int64(nBase-v))
	memstats.heapStats.release()

	// 更新页面分配器的结构，使此
	// 空间准备好分配。
	h.pages.grow(v, nBase-v)
	totalGrowth += nBase - v
	return totalGrowth, true
}

// 将跨度释放回堆中。
func (h *mheap) freeSpan(s *mspan) {
	systemstack(func() {
		lock(&h.lock)
		if msanenabled {
			// 告诉msan，整个跨度不再使用。
			base := unsafe.Pointer(s.base())
			bytes := s.npages << _PageShift
			msanfree(base, bytes)
		}
		if asanenabled {
			// 告诉阿桑，整个跨度不再使用。
			base := unsafe.Pointer(s.base())
			bytes := s.npages << _PageShift
			asanpoison(base, bytes)
		}
		h.freeSpanLocked(s, spanAllocHeap)
		unlock(&h.lock)
	})
}

// freeManual释放allocManual返回的手动管理的范围。
// 类型必须与传递给
// 分配给s的allocManual的spanAllocType相同。
// 
// 仅当gcphase=\u GCoff时才能调用此选项。有关
// 的说明，请参见mSpanState。
// 
// 必须在系统堆栈上调用freeManual，因为它获取了
// 堆锁。详见mheap。
// 
// go:systemstack 
func (h *mheap) freeManual(s *mspan, typ spanAllocType) {
	s.needzero = 1
	lock(&h.lock)
	h.freeSpanLocked(s, typ)
	unlock(&h.lock)
}

func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) {
	assertLockHeld(&h.lock)

	switch s.state.get() {
	case mSpanManual:
		if s.allocCount != 0 {
			throw("mheap.freeSpanLocked - invalid stack free")
		}
	case mSpanInUse:
		if s.allocCount != 0 || s.sweepgen != h.sweepgen {
			print("mheap.freeSpanLocked - span ", s, " ptr ", hex(s.base()), " allocCount ", s.allocCount, " sweepgen ", s.sweepgen, "/", h.sweepgen, "\n")
			throw("mheap.freeSpanLocked - invalid free")
		}
		h.pagesInUse.Add(-int64(s.npages))

		// 清除竞技场页面位图中的使用位。
		arena, pageIdx, pageMask := pageIndexOf(s.base())
		atomic.And8(&arena.pageInUse[pageIdx], ^pageMask)
	default:
		throw("mheap.freeSpanLocked - invalid span state")
	}

	// 更新统计信息。
	// 
	// 在AllocPan中镜像代码。
	nbytes := s.npages * pageSize
	if typ == spanAllocHeap {
		atomic.Xadd64(&memstats.heap_inuse, -int64(nbytes))
	}
	if typ.manual() {
		// 手动管理的内存不计入heap_sys，所以请将其添加回去。
		memstats.heap_sys.add(int64(nbytes))
	}
	// 更新一致的统计数据。
	stats := memstats.heapStats.acquire()
	switch typ {
	case spanAllocHeap:
		atomic.Xaddint64(&stats.inHeap, -int64(nbytes))
	case spanAllocStack:
		atomic.Xaddint64(&stats.inStacks, -int64(nbytes))
	case spanAllocPtrScalarBits:
		atomic.Xaddint64(&stats.inPtrScalarBits, -int64(nbytes))
	case spanAllocWorkBuf:
		atomic.Xaddint64(&stats.inWorkBufs, -int64(nbytes))
	}
	memstats.heapStats.release()

	// 将空间标记为空闲。
	h.pages.free(s.base(), s.npages, false)

	// 释放跨度结构。我们不再使用它了。
	s.state.set(mSpanDead)
	h.freeMSpanLocked(s)
}

// 清除所有获取堆锁（阻止任何额外的
// 对页面分配器的操作）并迭代整个
// 堆，清除所有可用的空闲页面。
func (h *mheap) scavengeAll() {
	// 在持有堆锁时不允许malloc或panic。我们在这里使用
	// 因为这是mheap API的非mallocgc入口点。
	gp := getg()
	gp.m.mallocing++

	lock(&h.lock)
	// 开始新一代清道夫，这样我们就有机会在整个堆上行走。
	h.pages.scavengeStartGen()
	unlock(&h.lock)

	released := h.pages.scavenge(^uintptr(0))

	lock(&h.pages.scav.lock)
	gen := h.pages.scav.gen
	unlock(&h.pages.scav.lock)

	gp.m.mallocing--

	if debug.scavtrace > 0 {
		printScavTrace(gen, released, true)
	}
}

// go:linkname运行时_调试_freeOSMemory运行时/调试。freeOSMemory 
func runtime_debug_freeOSMemory() {
	GC()
	systemstack(func() { mheap_.scavengeAll() })
}

// 使用给定的起始和npages初始化一个新的跨度。
func (span *mspan) init(base uintptr, npages uintptr) {
	// span*未*归零。
	span.next = nil
	span.prev = nil
	span.list = nil
	span.startAddr = base
	span.npages = npages
	span.allocCount = 0
	span.spanclass = 0
	span.elemsize = 0
	span.speciallock.key = 0
	span.specials = nil
	span.needzero = 0
	span.freeindex = 0
	span.allocBits = nil
	span.gcmarkBits = nil
	span.state.set(mSpanDead)
	lockInit(&span.speciallock, lockRankMspanSpecial)
}

func (span *mspan) inList() bool {
	return span.list != nil
}

// 初始化空的双链接列表。
func (list *mSpanList) init() {
	list.first = nil
	list.last = nil
}

func (list *mSpanList) remove(span *mspan) {
	if span.list != list {
		print("runtime: failed mSpanList.remove span.npages=", span.npages,
			" span=", span, " prev=", span.prev, " span.list=", span.list, " list=", list, "\n")
		throw("mSpanList.remove")
	}
	if list.first == span {
		list.first = span.next
	} else {
		span.prev.next = span.next
	}
	if list.last == span {
		list.last = span.prev
	} else {
		span.next.prev = span.prev
	}
	span.next = nil
	span.prev = nil
	span.list = nil
}

func (list *mSpanList) isEmpty() bool {
	return list.first == nil
}

func (list *mSpanList) insert(span *mspan) {
	if span.next != nil || span.prev != nil || span.list != nil {
		println("runtime: failed mSpanList.insert", span, span.next, span.prev, span.list)
		throw("mSpanList.insert")
	}
	span.next = list.first
	if list.first != nil {
		// 列表至少包含一个跨度；把它连接起来。
		// 列表中的最后一个跨度不变。
		list.first.prev = span
	} else {
		// 列表中没有跨距，因此这也是最后一个跨距。
		list.last = span
	}
	list.first = span
	span.list = list
}

func (list *mSpanList) insertBack(span *mspan) {
	if span.next != nil || span.prev != nil || span.list != nil {
		println("runtime: failed mSpanList.insertBack", span, span.next, span.prev, span.list)
		throw("mSpanList.insertBack")
	}
	span.prev = list.last
	if list.last != nil {
		// 列表至少包含一个跨度。
		list.last.next = span
	} else {
		// 列表中没有跨距，因此这也是第一个跨距。
		list.first = span
	}
	list.last = span
	span.list = list
}

// takeAll删除其他所有跨距，并将它们插入列表的前面。
func (list *mSpanList) takeAll(other *mSpanList) {
	if other.isEmpty() {
		return
	}

	// 将其他列表中的所有内容重新打包。
	for s := other.first; s != nil; s = s.next {
		s.list = list
	}

	// 连接列表。
	if list.isEmpty() {
		*list = *other
	} else {
		// 两个列表都不是空的。把其他的放在列表之前。
		other.last.next = list.first
		list.first.prev = other.last
		list.first = other.first
	}

	other.first, other.last = nil, nil
}

const (
	_KindSpecialFinalizer = 1
	_KindSpecialProfile   = 2
	// /_KindSpecialReachable是一款用于跟踪测试期间可达性的专用工具。
	_KindSpecialReachable = 3
	// 注意：终结器专用必须是第一个，因为如果我们释放
	// 对象，终结器专用将导致释放操作
	// 中止，如果发生这种情况，我们希望保留
	// 周围的其他特殊记录。
)

// go:notinheap 
type special struct {
	next   *special // 在span 
	offset uint16   // 对象的span偏移量
	kind   byte     // 一种特殊的
}

// span在竞技场位图中将span标记为具有特殊性。
func spanHasSpecials(s *mspan) {
	arenaPage := (s.base() / pageSize) % pagesPerArena
	ai := arenaIndex(s.base())
	ha := mheap_.arenas[ai.l1()][ai.l2()]
	atomic.Or8(&ha.pageSpecials[arenaPage/8], uint8(1)<<(arenaPage%8))
}

// spanHasNoSpecials将span标记为在竞技场中没有特殊物品。
func spanHasNoSpecials(s *mspan) {
	arenaPage := (s.base() / pageSize) % pagesPerArena
	ai := arenaIndex(s.base())
	ha := mheap_.arenas[ai.l1()][ai.l2()]
	atomic.And8(&ha.pageSpecials[arenaPage/8], ^(uint8(1) << (arenaPage % 8)))
}

// 将特殊记录s添加到
// 对象p的特殊记录列表中。除此例程将填写的
// 偏移量和下一步外，应填写s的所有字段。
// 如果特殊项添加成功，则返回true，否则返回false。
// （只有当具有相同p和s->kind 
// 的记录已经存在时，添加才会失败。）
func addspecial(p unsafe.Pointer, s *special) bool {
	span := spanOfHeap(uintptr(p))
	if span == nil {
		throw("addspecial on invalid pointer")
	}

	// 确保跨度被扫掠。
	// Sweeping访问不带锁的特殊列表，因此我们有
	// 与之同步。这样更安全。
	mp := acquirem()
	span.ensureSwept()

	offset := uintptr(p) - span.base()
	kind := s.kind

	lock(&span.speciallock)

	// 找到拼接点，检查现有记录。
	t := &span.specials
	for {
		x := *t
		if x == nil {
			break
		}
		if offset == uintptr(x.offset) && kind == x.kind {
			unlock(&span.speciallock)
			releasem(mp)
			return false // 已存在
		}
		if offset < uintptr(x.offset) || (offset == uintptr(x.offset) && kind < x.kind) {
			break
		}
		t = &x.next
	}

	// 记录中的拼接，填写偏移量。
	s.offset = uint16(offset)
	s.next = *t
	*t = s
	spanHasSpecials(span)
	unlock(&span.speciallock)
	releasem(mp)

	return true
}

// 删除对象p的给定类型的特殊记录。
// 如果记录存在，则返回该记录，否则返回零。
// 调用者必须修复释放结果。
func removespecial(p unsafe.Pointer, kind uint8) *special {
	span := spanOfHeap(uintptr(p))
	if span == nil {
		throw("removespecial on invalid pointer")
	}

	// 确保跨度被扫过。
	// 扫除访问不带锁的特殊列表，因此我们有
	// 与之同步。这样更安全。
	mp := acquirem()
	span.ensureSwept()

	offset := uintptr(p) - span.base()

	var result *special
	lock(&span.speciallock)
	t := &span.specials
	for {
		s := *t
		if s == nil {
			break
		}
		// 此函数仅用于终结器，因此我们不检查
		// “内部”特殊值（p必须完全等于s->offset）。
		if offset == uintptr(s.offset) && kind == s.kind {
			*t = s.next
			result = s
			break
		}
		t = &s.next
	}
	if span.specials == nil {
		spanHasNoSpecials(span)
	}
	unlock(&span.speciallock)
	releasem(mp)
	return result
}

// 所描述的对象设置了终结器。
// 
// specialfinalizer是从非GC的内存分配的，因此任何堆
// 指针都必须经过特殊处理。
// 
// go:notinheap 
type specialfinalizer struct {
	special special
	fn      *funcval // 可能是堆指针。
	nret    uintptr
	fint    *_type   // 可能是堆指针，但始终有效。
	ot      *ptrtype // 可能是堆指针，但始终有效。
}

// 向对象p添加终结器。如果成功，则返回true。
func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *_type, ot *ptrtype) bool {
	lock(&mheap_.speciallock)
	s := (*specialfinalizer)(mheap_.specialfinalizeralloc.alloc())
	unlock(&mheap_.speciallock)
	s.special.kind = _KindSpecialFinalizer
	s.fn = f
	s.nret = nret
	s.fint = fint
	s.ot = ot
	if addspecial(p, &s.special) {
		// 这负责在任何
		// GC相关不变量，其中markrootSpans 
		// 情况下维护与markrootSpans相同的
		// 可能已经运行，但标记终止尚未运行。
		if gcphase != _GCoff {
			base, _, _ := findObject(uintptr(p), 0, 0)
			mp := acquirem()
			gcw := &mp.p.ptr().gcw
			// 标记从对象
			// 可以访问的所有内容，以便保留给终结器。
			scanobject(base, gcw)
			// 标记终结器本身，因为
			// special不属于GC的堆。
			scanblock(uintptr(unsafe.Pointer(&s.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
			releasem(mp)
		}
		return true
	}

	// 有一个旧的终结器
	lock(&mheap_.speciallock)
	mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))
	unlock(&mheap_.speciallock)
	return false
}

// 从对象中删除终结器（如果有）。
func removefinalizer(p unsafe.Pointer) {
	s := (*specialfinalizer)(unsafe.Pointer(removespecial(p, _KindSpecialFinalizer)))
	if s == nil {
		return // 没有一个终结器删除
	}
	lock(&mheap_.speciallock)
	mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))
	unlock(&mheap_.speciallock)
}

// 所描述的对象正在进行堆分析。
// 
// go:notinheap 
type specialprofile struct {
	special special
	b       *bucket
}

// 将与addr关联的堆配置文件存储桶设置为b。
func setprofilebucket(p unsafe.Pointer, b *bucket) {
	lock(&mheap_.speciallock)
	s := (*specialprofile)(mheap_.specialprofilealloc.alloc())
	unlock(&mheap_.speciallock)
	s.special.kind = _KindSpecialProfile
	s.b = b
	if !addspecial(p, &s.special) {
		throw("setprofilebucket: profile already set")
	}
}

// specialReachable跟踪对象在下一个
// GC循环中是否可访问。这用于测试。
type specialReachable struct {
	special   special
	done      bool
	reachable bool
}

// specialsIter帮助迭代特价商品列表。
type specialsIter struct {
	pprev **special
	s     *special
}

func newSpecialsIter(span *mspan) specialsIter {
	return specialsIter{&span.specials, span.specials}
}

func (i *specialsIter) valid() bool {
	return i.s != nil
}

func (i *specialsIter) next() {
	i.pprev = &i.s.next
	i.s = *i.pprev
}

// unlinkAndNext从列表中删除当前特殊项，并将迭代器移动到下一个特殊项。它返回未链接的特殊值。
func (i *specialsIter) unlinkAndNext() *special {
	cur := i.s
	i.s = cur.next
	*i.pprev = i.s
	return cur
}

// freeSpecial对特殊s执行任何清理并解除分配。
// s必须已从特价商品列表中取消链接。
func freeSpecial(s *special, p unsafe.Pointer, size uintptr) {
	switch s.kind {
	case _KindSpecialFinalizer:
		sf := (*specialfinalizer)(unsafe.Pointer(s))
		queuefinalizer(p, sf.fn, sf.nret, sf.fint, sf.ot)
		lock(&mheap_.speciallock)
		mheap_.specialfinalizeralloc.free(unsafe.Pointer(sf))
		unlock(&mheap_.speciallock)
	case _KindSpecialProfile:
		sp := (*specialprofile)(unsafe.Pointer(s))
		mProf_Free(sp.b, size)
		lock(&mheap_.speciallock)
		mheap_.specialprofilealloc.free(unsafe.Pointer(sp))
		unlock(&mheap_.speciallock)
	case _KindSpecialReachable:
		sp := (*specialReachable)(unsafe.Pointer(s))
		sp.done = true
		// 造物主释放了这些。
	default:
		throw("bad special kind")
		panic("not reached")
	}
}

// gcBits是一种alloc/mark位图。这总是用作*gcbit。
// 
// go:notinheap 
type gcBits uint8

// bytep返回指向b的第n个字节的指针。
func (b *gcBits) bytep(n uintptr) *uint8 {
	return addb((*uint8)(b), n)
}

// bitp返回指向包含位n和
func (b *gcBits) bitp(n uintptr) (bytep *uint8, mask uint8) {
	return b.bytep(n / 8), 1 << (n % 8)
}

const gcBitsChunkBytes = uintptr(64 << 10)
const gcBitsHeaderBytes = unsafe.Sizeof(gcBitsHeader{})

type gcBitsHeader struct {
	free uintptr // free是下一个空闲字节的位索引。
	next uintptr // /*gcBits触发递归类型错误。（第14620期）
}

// go:notinheap 
type gcBitsArena struct {
	// gcBitsHeader 
	free uintptr // free是下一个空闲字节的位索引；原子读/写
	next *gcBitsArena
	bits [gcBitsChunkBytes - gcBitsHeaderBytes]gcBits
}

var gcBitsArenas struct {
	lock     mutex
	free     *gcBitsArena
	next     *gcBitsArena // 原子读。在锁定状态下按原子顺序书写。
	current  *gcBitsArena
	previous *gcBitsArena
}

// tryAlloc从b分配，如果b没有足够的空间，则返回零。
// 可以同时调用。
func (b *gcBitsArena) tryAlloc(bytes uintptr) *gcBits {
	if b == nil || atomic.Loaduintptr(&b.free)+bytes > uintptr(len(b.bits)) {
		return nil
	}
	// 尝试从此块分配。有足够的空间。
	end := atomic.Xadduintptr(&b.free, bytes)
	if end > uintptr(len(b.bits)) {
		return nil
	}
	start := end - bytes
	return &b.bits[start]
}

// newMarkBits返回指向8字节对齐字节的指针
// 用于span的标记位。
func newMarkBits(nelems uintptr) *gcBits {
	blocksNeeded := uintptr((nelems + 63) / 64)
	bytesNeeded := blocksNeeded * 8

	// 尝试直接从当前head arena分配。
	head := (*gcBitsArena)(atomic.Loadp(unsafe.Pointer(&gcBitsArenas.next)))
	if p := head.tryAlloc(bytesNeeded); p != nil {
		return p
	}

	// 头部竞技场没有足够的空间。我们可能需要
	// 分配一个新的竞技场。
	lock(&gcBitsArenas.lock)
	// 再次尝试head arena，因为它可能已经改变了。现在
	// 我们持有锁，列表头不能更改，但其
	// 自由位置仍然可以更改。
	if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
		unlock(&gcBitsArenas.lock)
		return p
	}

	// 分配一个新竞技场。这可能会暂时关闭锁。
	fresh := newArenaMayUnlock()
	// 如果newArenaMayUnlock放弃了锁，另一个线程可能会
	// 将一个新的竞技场放在“下一个”列表中。再次尝试从下一步分配
	// 。
	if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
		// 将fresh重新列入免费名单。
		// 待办事项：将其标记为“已归零”
		fresh.next = gcBitsArenas.free
		gcBitsArenas.free = fresh
		unlock(&gcBitsArenas.lock)
		return p
	}

	// 从新竞技场分配。我们还没有把它联系起来，所以
	// 这不可能比赛，而且肯定会成功。
	p := fresh.tryAlloc(bytesNeeded)
	if p == nil {
		throw("markBits overflow")
	}

	// 将新竞技场添加到“下一个”列表中。
	fresh.next = gcBitsArenas.next
	atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), unsafe.Pointer(fresh))

	unlock(&gcBitsArenas.lock)
	return p
}

// newAllocBits返回一个指向8字节对齐字节的指针
// 用于此跨度的alloc位。
// newAllocBits用于提供新初始化的跨距
// 分配位。对于未初始化的跨距，当扫描跨距时，标记位被重新用作分配位。
func newAllocBits(nelems uintptr) *gcBits {
	return newMarkBits(nelems)
}

// NEXTMARKBITARENEPOCH为竞技场建立了一个新纪元
// 持有标志位。竞技场的命名与当前GC循环有关，当前GC循环通过调用finishweep_m来划分。所有当前跨度均已扫过。
// 在扫描过程中，每个跨度都为其在
// gcBitsArenas中的gcmarkBits分配了空间。下一个街区。gcBitsArenas。接下来是gcBitsArenas。当前
// GC将在其中标记对象，在扫描每个跨度后，这些位
// 将用于分配对象。
// gcBitsArenas。电流变为gcBitsArenas。在此之前，该跨度的
// gcAllocBits一直处于活动状态，直到在该GC循环中所有跨度都被扫过为止。
// span的扫描消除了对gcBitsArenas的所有引用。之前的
// 通过将gcAllocBits指向gcBitsArenas。现在的
// gcBitsArenas。之前的版本发布到gcBitsArenas。免费名单。
func nextMarkBitArenaEpoch() {
	lock(&gcBitsArenas.lock)
	if gcBitsArenas.previous != nil {
		if gcBitsArenas.free == nil {
			gcBitsArenas.free = gcBitsArenas.previous
		} else {
			// 找到以前竞技场的终点。
			last := gcBitsArenas.previous
			for last = gcBitsArenas.previous; last.next != nil; last = last.next {
			}
			last.next = gcBitsArenas.free
			gcBitsArenas.free = gcBitsArenas.previous
		}
	}
	gcBitsArenas.previous = gcBitsArenas.current
	gcBitsArenas.current = gcBitsArenas.next
	atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), nil) // newMarkBits在需要时调用newArena。
	unlock(&gcBitsArenas.lock)
}

// 调用者必须持有gcBitsArena。锁这可能会暂时释放它。
func newArenaMayUnlock() *gcBitsArena {
	var result *gcBitsArena
	if gcBitsArenas.free == nil {
		unlock(&gcBitsArenas.lock)
		result = (*gcBitsArena)(sysAlloc(gcBitsChunkBytes, &memstats.gcMiscSys))
		if result == nil {
			throw("runtime: cannot allocate memory")
		}
		lock(&gcBitsArenas.lock)
	} else {
		result = gcBitsArenas.free
		gcBitsArenas.free = gcBitsArenas.free.next
		memclrNoHeapPointers(unsafe.Pointer(result), gcBitsChunkBytes)
	}
	result.next = nil
	// If结果。位不是8字节对齐的调整索引，所以
	// 这是结果（&R）。位[result.free]是8字节对齐的。
	if uintptr(unsafe.Offsetof(gcBitsArena{}.bits))&7 == 0 {
		result.free = 0
	} else {
		result.free = 8 - (uintptr(unsafe.Pointer(&result.bits[0])) & 7)
	}
	return result
}
